/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { AnyType, ArkAssignStmt, ArkFile, ArkMethod, Local, Stmt, Type } from "arkanalyzer";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { MatcherCallback, MatcherTypes, MethodMatcher } from "../../matcher/Matchers";
import { Defects, IssueReport } from "../../model/Defects";
import { RuleFix } from "../../model/Fix";
import { Rule } from "../../model/Rule";
import { isAppointType } from "../../utils/checker/TypeUtils";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { BaseChecker, BaseMetaData } from "../BaseChecker";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoExplicitAnyCheck');
const gMetaData: BaseMetaData = {
  severity: 2,
  ruleDocPath: "docs/no-explicit-any-check.md",
  description: "Require Promise-like statements to be handled appropriately"
};

const messages = {
    suggestNever:
      "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of.",
    suggestUnknown:
      'Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct.',
    unexpectedAny: 'Unexpected any. Specify a different type.',
  }

export class NoExplicitAnyCheck implements BaseChecker {
  public issues: IssueReport[] = [];
  public defects: Defects[] = [];
  readonly metaData: BaseMetaData = gMetaData;
  public rule: Rule;
  private methodMatcher: MethodMatcher = {
    matcherType: MatcherTypes.METHOD,
  };

  public registerMatchers(): MatcherCallback[] {
    const methodMatcherCb: MatcherCallback = {
      matcher: this.methodMatcher,
      callback: this.check
    }
    return [methodMatcherCb];
  }

  private defaultOption: { "ignoreRestArgs"?: boolean, "fixToUnknown"?: boolean } 
      = { "ignoreRestArgs": false, "fixToUnknown": false };
  private option: { "ignoreRestArgs"?: boolean, "fixToUnknown"?: boolean } = this.defaultOption;

  public check = (target: ArkMethod) => {

    const originPath = target.getDeclaringArkClass().getDeclaringArkFile().getName();
    if (!this.isTsFile(originPath)) {
        return;
    }

    if (this.rule && this.rule.option && this.rule.option[0]) {
      this.option = this.rule.option[0] as { "ignoreRestArgs"?: boolean, "fixToUnknown"?: boolean };
    }

    if (this.option.ignoreRestArgs) return;

    let methodSubSignature = target.getSignature().getMethodSubSignature();
    methodSubSignature.getParameters().forEach((param, index) => {
      if (this.isAnyType(param.getType())) {
        this.addIssueReport(target.getDeclaringArkFile(), target);
      }
    });

    if (this.isAnyType(methodSubSignature.getReturnType())) {
      this.addIssueReport(target.getDeclaringArkFile(), target);
    }

    let stmts = target.getBody()?.getCfg()?.getStartingBlock()?.getStmts() ?? [];
    stmts.forEach((stmt) => {
      if (stmt instanceof ArkAssignStmt) {
        const assignStmt = stmt as ArkAssignStmt;
        const leftOp = assignStmt.getLeftOp();
        if (leftOp instanceof Local && !(leftOp as Local).getName().startsWith("%") && this.isAnyType(leftOp.getType())) {
          this.addIssueReport(target.getDeclaringArkFile(), stmt);
        }
      }
    });
  }

  private isAnyType(type: Type): boolean {
    return isAppointType(AnyType.getInstance(), type);
  }

  private addIssueReport(arkFile: ArkFile, stmt: Stmt | ArkMethod) {
      const posArr = this.getLineAndColumn(stmt, 'any');
      posArr.forEach((pos)=>{
        const defect = this.createDefect(pos, arkFile.getFilePath());
        if (!defect) {
            return;
        }
        //创建fix
        let fix = this.createFix(arkFile, stmt, pos);
        this.issues.push(new IssueReport(defect, fix));
        RuleListUtil.push(defect);
      });
  }

  private createDefect(pos: { line: number, start: number }, filePath: string, message?: string) {
    const msg = message ?? messages.unexpectedAny;
    const severity = this.rule.alert ?? this.metaData.severity;
    return new Defects(pos.line, pos.start, pos.start, msg, severity, this.rule.ruleId,
      filePath, this.metaData.ruleDocPath, true, false, true);
  }

  private createFix(arkFile: ArkFile, stmt: Stmt | ArkMethod, pos: { line: number, start: number }): RuleFix | undefined {
      let start = this.getLineStartPosition(arkFile.getCode(), pos.line) ?? 0;
      return {range: [start + pos.start - 1, start + pos.start - 1 + 3], text: 'unknown'};
  }

  private getLineStartPosition(sourceCode: string, lineNumber: number): number | null {
    // 将字符串按行分割成数组
    const lines = sourceCode.split('\n');
    
    // 检查行号是否有效
    if (lineNumber < 1 || lineNumber > lines.length) {
        return null; // 行号无效，返回 null
    }
    
    // 计算指定行的起始位置
    let position = 0;
    for (let i = 0; i < lineNumber - 1; i++) {
        position += lines[i].length + 1; // 加 1 是为了包括换行符
    }
    
    return position;
  }

  private getLineAndColumn(data: ArkMethod|Stmt, text: string): {line: number, start: number}[] {//text: string, isReadonly: boolean
    const arr:{line: number, start: number}[] = [];
    let line: number = -1;
    if (data instanceof ArkMethod) {
      line = data.getLine() ?? 0;
      let arkFile = data.getDeclaringArkFile();
      if (arkFile) {
        let startCol = data.getColumn() ?? 0;
        let posArr = this.getAllIndices(data.getCode()??'', text);
        posArr.forEach((pos)=>{
          if (pos && pos !== -1) {
            let start = startCol + pos
            arr.push({ line, start});
          }
        });
      } else {
        logger.debug('originMethod or arkFile is null');
      }
    } else if (data instanceof Stmt) {
      const originPosition = data.getOriginPositionInfo();
      line = originPosition.getLineNo();

      const arkFile = data.getCfg()?.getDeclaringMethod().getDeclaringArkFile();
      if (arkFile) {
        let startCol = originPosition.getColNo();
        let posArr = this.getAllIndices(data.getOriginalText()??'', text);
        posArr.forEach((pos)=>{
          if (pos && pos !== -1) {
            let start = startCol + pos
            arr.push({ line, start});
          }
        });
      } else {
        logger.debug('originStmt or arkFile is null');
      }
    }
    return arr;
  }

  private getAllIndices(source: string, targetChar: string): number[] {
    const indices: number[] = []; // 存储所有匹配的下标
    let currentIndex = 0; // 当前查找的起始位置

    // 检查目标字符是否为空
    if (targetChar.length === 0) {
        return indices; // 如果目标字符为空，返回空数组
    }

    // 循环查找所有匹配的下标
    while (true) {
        const index = source.indexOf(targetChar, currentIndex);
        if (index === -1) {
            break; // 如果没有找到更多匹配项，退出循环
        }
        indices.push(index); // 将找到的下标添加到数组中
        currentIndex = index + 1; // 更新查找的起始位置
    }

    return indices;
  }

  private isTsFile(filePath: string): boolean {
        return filePath.toLowerCase().endsWith('.ts');
  }
}
